/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Mar 8, 2011
 */

package com.bigdata.util.httpd;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import junit.framework.TestCase2;

import com.bigdata.util.Bytes;
import com.bigdata.util.config.NicUtil;

/**
 * Test suite for {@link NanoHTTPD}.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
public class TestNanoHTTPD extends TestCase2 {

    /**
     * 
     */
    public TestNanoHTTPD() {
    }

    /**
     * @param name
     */
    public TestNanoHTTPD(String name) {
        super(name);
    }

    public void test_startShutdownNow() throws IOException {

        final NanoHTTPD fixture = new NanoHTTPD(0/* port */);

        try {

            assertNotSame("port", 0, fixture.getPort());

            assertTrue("open", fixture.isOpen());

        } finally {

            fixture.shutdownNow();

        }

        assertFalse("open", fixture.isOpen());

    }

    public void test_startShutdown() throws IOException, InterruptedException,
            ExecutionException, TimeoutException {

        final NanoHTTPD fixture = new NanoHTTPD(0/* port */);

        try {

            final ExecutorService executor = Executors
                    .newSingleThreadScheduledExecutor();

            try {

                assertNotSame("port", 0, fixture.getPort());

                assertTrue("open", fixture.isOpen());

                final FutureTask<Void> ft = new FutureTask<Void>(
                        new Runnable() {
                            public void run() {
                                fixture.shutdown();
                            }
                        }, null/* Void */);

                executor.submit(ft);

                // wait up to a timeout for the service to terminate.
                ft.get(1000L, TimeUnit.MILLISECONDS);

                assertFalse("open", fixture.isOpen());

            } finally {

                executor.shutdownNow();

            }

        } finally {

            fixture.shutdownNow();

        }

        assertFalse("open", fixture.isOpen());

    }

    /*
     * GET
     */
    
    public void test_get() throws IOException {

        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {

            @Override
            public Response serve(final Request req) {

                if (GET.equalsIgnoreCase(req.method)) {

                    // One signal for GET .
                    return new Response(HTTP_OK, MIME_TEXT_PLAIN, "");

                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
            // final int timeout = 0;// timeout (ms).
            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final URL url = new URL("http", hostAddr, port, ""/* file */);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);
                // conn.setRequestProperty("Accept", MIME_RDF_XML);

                // connect.
                conn.connect();

                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                // if (log.isInfoEnabled())
                // log.info("Status Line: " + conn.getResponseMessage());

                /*
                 * Write out the response body
                 */
                {

                    final LineNumberReader r = new LineNumberReader(
                            new InputStreamReader(
                                    conn.getInputStream(),
                                    conn.getContentEncoding() == null ? "ISO-8859-1"
                                            : conn.getContentEncoding()));
                    try {
                        String s;
                        while ((s = r.readLine()) != null) {
                            if (log.isInfoEnabled())
                                log.info(s);
                        }
                    } finally {
                        r.close();
                    }

                }

                // final long elapsed = System.nanoTime() - begin;

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

    /**
     * Unit test for URL query parameter decode for GET.
     */
    public void test_getDecodeParams() throws IOException {

        final LinkedHashMap<String, Vector<String>> expected = new LinkedHashMap<String, Vector<String>>();
        {
            {
                final Vector<String> v = new Vector<String>();
                v.add("1");
                v.add("2");
                v.add("3");
                expected.put("a", v);
            }
            {
                final Vector<String> v = new Vector<String>();
                v.add("a b c");
                v.add("d e");
                v.add("f");
                expected.put("blue", v);
            }
        }
        
        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {
            
            @Override
            public Response serve(final Request req) {

                if (GET.equalsIgnoreCase(req.method)) {

                    assertEquals("size", expected.size(), req.params.size());

                    for (Map.Entry<String, Vector<String>> e : expected
                            .entrySet()) {
                    
                        assertEquals(e.getKey(), expected.get(e.getKey()),
                                req.params.get(e.getKey()));
                        
                    }
                    
                    // One signal for GET .
                    return new Response(HTTP_OK, MIME_TEXT_PLAIN, "");

                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
            // final int timeout = 0;// timeout (ms).
            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final StringBuilder queryParms = NanoHTTPD.encodeParams(expected);

            final URL url = new URL("http", hostAddr, port, "/?" + queryParms);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);
                // conn.setRequestProperty("Accept", MIME_RDF_XML);

                // connect.
                conn.connect();

                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                // if (log.isInfoEnabled())
                // log.info("Status Line: " + conn.getResponseMessage());

                /*
                 * Write out the response body
                 */
                {

                    final LineNumberReader r = new LineNumberReader(
                            new InputStreamReader(
                                    conn.getInputStream(),
                                    conn.getContentEncoding() == null ? "ISO-8859-1"
                                            : conn.getContentEncoding()));
                    try {
                        String s;
                        while ((s = r.readLine()) != null) {
                            if (log.isInfoEnabled())
                                log.info(s);
                        }
                    } finally {
                        r.close();
                    }

                }

                // final long elapsed = System.nanoTime() - begin;

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

    public void test_getStream() throws IOException {

        final byte[] data = new byte[Bytes.kilobyte32 * 8];
        {
        
            final Random r = new Random();

            r.nextBytes(data);
            
        }

        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {
            
            @Override
            public Response serve(final Request req) {

                if (GET.equalsIgnoreCase(req.method)) {

                    // One signal for GET .
                    return new Response(HTTP_OK, MIME_DEFAULT_BINARY,
                            new ByteArrayInputStream(data));

                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
            // final int timeout = 0;// timeout (ms).
            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final URL url = new URL("http", hostAddr, port, ""/* file */);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);
                // conn.setRequestProperty("Accept", MIME_RDF_XML);

                // connect.
                conn.connect();

                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                // if (log.isInfoEnabled())
                // log.info("Status Line: " + conn.getResponseMessage());

                /*
                 * Read the response body and compare it with the expected data.
                 */
                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                {

                    final InputStream is = conn.getInputStream();

                    try {
                        int b;
                        while ((b = is.read()) != -1) {
                            baos.write(b);
                        }
                    } finally {
                        is.close();
                    }

                    baos.flush();
                }

                assertEquals(data.length, baos.size());
                assertEquals(data, baos.toByteArray());

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

    /*
     * POST
     */
    
    /**
     * Post with an empty request body.
     */
    public void test_post_emptyBody() throws IOException {

        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {

            @Override
            public Response serve(final Request req) {

                if (POST.equalsIgnoreCase(req.method)) {

                    // One signal for POST.
                    return new Response(HTTP_OK, MIME_TEXT_PLAIN, "");

                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
             final int timeout = 0;// timeout (ms).
//            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final URL url = new URL("http", hostAddr, port, ""/* file */);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);
                // conn.setRequestProperty("Accept", MIME_RDF_XML);

                // connect.
                conn.connect();
                
                // send an empty POST body.
                conn.getOutputStream().close();
                
                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                // if (log.isInfoEnabled())
                // log.info("Status Line: " + conn.getResponseMessage());

                /*
                 * Read the response body
                 */
                {

                    final LineNumberReader r = new LineNumberReader(
                            new InputStreamReader(
                                    conn.getInputStream(),
                                    conn.getContentEncoding() == null ? "ISO-8859-1"
                                            : conn.getContentEncoding()));
                    try {
                        String s;
                        while ((s = r.readLine()) != null) {
                            if (log.isInfoEnabled())
                                log.info(s);
                        }
                    } finally {
                        r.close();
                    }

                }

                // final long elapsed = System.nanoTime() - begin;

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

    /**
     * Post with <code>application/x-www-form-urlencoded</code> parameters in a
     * request body.
     */
    public void test_post_application_x_www_form_urlencoded() throws IOException {

        final LinkedHashMap<String, Vector<String>> expected = new LinkedHashMap<String, Vector<String>>();
        {
            {
                final Vector<String> v = new Vector<String>();
                v.add("1");
                v.add("2");
                v.add("3");
                expected.put("a", v);
            }
            {
                final Vector<String> v = new Vector<String>();
                v.add("a b c");
                v.add("d e");
                v.add("f");
                expected.put("blue", v);
            }
        }

        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {

            @Override
            public Response serve(final Request req) {

                if (POST.equalsIgnoreCase(req.method)) {

                    assertEquals("size", expected.size(), req.params.size());

                    for (Map.Entry<String, Vector<String>> e : expected
                            .entrySet()) {
                    
                        assertEquals(e.getKey(), expected.get(e.getKey()),
                                req.params.get(e.getKey()));
                        
                    }
                    
                    // One signal for POST.
                    return new Response(HTTP_OK, MIME_TEXT_PLAIN, "");

                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
             final int timeout = 0;// timeout (ms).
//            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final StringBuilder queryParms = NanoHTTPD.encodeParams(expected);

            final URL url = new URL("http", hostAddr, port, "/?" + queryParms);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(false);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);
                // conn.setRequestProperty("Accept", MIME_RDF_XML);

                // connect.
                conn.connect();
                
                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                // if (log.isInfoEnabled())
                // log.info("Status Line: " + conn.getResponseMessage());

                /*
                 * Read the response body
                 */
                {

                    final LineNumberReader r = new LineNumberReader(
                            new InputStreamReader(
                                    conn.getInputStream(),
                                    conn.getContentEncoding() == null ? "ISO-8859-1"
                                            : conn.getContentEncoding()));
                    try {
                        String s;
                        while ((s = r.readLine()) != null) {
                            if (log.isInfoEnabled())
                                log.info(s);
                        }
                    } finally {
                        r.close();
                    }

                }

                // final long elapsed = System.nanoTime() - begin;

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

    /**
     * Post with a non-empty request body using
     * <code>application/octet-stream</code>. The request body is written using
     * the raw {@link OutputStream}.
     */
    public void test_post_application_octet_stream_usingOutputStream()
            throws IOException {

        final byte[] expected = "Hello World!".getBytes();
        
        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {

            @Override
            public Response serve(final Request req) {

                if (POST.equalsIgnoreCase(req.method)) {

                    try {

                        final byte[] actual = req.getBinaryContent();
                        
                        assertEquals(expected, actual);
                        
                        // One signal for POST.
                        return new Response(HTTP_OK, MIME_TEXT_PLAIN, "");
                        
                    } catch (IOException e) {
                        
                        throw new RuntimeException(e);

                    }
                    
                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
//             final int timeout = 0;// timeout (ms).
            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final URL url = new URL("http", hostAddr, port, ""/* file */);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);

                // send an POST body.
                {
                    conn.addRequestProperty("Content-Type",
                            NanoHTTPD.MIME_DEFAULT_BINARY);
                    final OutputStream os = conn.getOutputStream();
                    os.write(expected);
                    os.flush();
                    os.close();
                }
                // connect.
                conn.connect();

                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                /*
                 * Read the response body
                 */
                {

                    final LineNumberReader r = new LineNumberReader(
                            new InputStreamReader(
                                    conn.getInputStream(),
                                    conn.getContentEncoding() == null ? "ISO-8859-1"
                                            : conn.getContentEncoding()));
                    try {
                        String s;
                        while ((s = r.readLine()) != null) {
                            if (log.isInfoEnabled())
                                log.info(s);
                        }
                    } finally {
                        r.close();
                    }

                }

                // final long elapsed = System.nanoTime() - begin;

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

    /**
     * Post with a non-empty request body using
     * <code>application/octet-stream</code>. The request body is written using
     * a {@link Writer}.
     * 
     * @throws IOException
     */
    public void test_post_application_octet_stream_usingWriter() throws IOException {

        final String expected = "Hello World!";
        
        final NanoHTTPD fixture = new NanoHTTPD(0/* port */) {

            @Override
            public Response serve(final Request req) {

                if (POST.equalsIgnoreCase(req.method)) {

                    try {

                        final String actual = req.getStringContent();
                        
                        assertEquals(expected, actual);
                        
                        // One signal for POST.
                        return new Response(HTTP_OK, MIME_TEXT_PLAIN, "");
                        
                    } catch (IOException e) {
                        
                        throw new RuntimeException(e);

                    }
                    
                }

                // Another for any other method or resource.
                return new Response(HTTP_INTERNALERROR, MIME_TEXT_PLAIN, 
                        req.method);

            }

        };

        try {

            // Note: Use ZERO (0) for an infinite timeout to debug.
             final int timeout = 0;// timeout (ms).
//            final int timeout = 1000;// timeout (ms).

            final int port = fixture.getPort();

            final String hostAddr = NicUtil.getIpAddress("default.nic",
                    "default", true/* loopbackOk */);

            final URL url = new URL("http", hostAddr, port, ""/* file */);

            if (log.isInfoEnabled())
                log.info("url: " + url);

            HttpURLConnection conn = null;
            try {

                conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
                conn.setUseCaches(false);
                conn.setReadTimeout(timeout);
                // conn.setRequestProperty("Accept", MIME_RDF_XML);

                // send an POST body.
                {
                    conn.addRequestProperty("Content-Type",
                            NanoHTTPD.MIME_TEXT_PLAIN);
                    final OutputStream os = conn.getOutputStream();
                    final Writer w = new OutputStreamWriter(os);
                    w.write(expected);
                    w.flush();
                    w.close();
                    os.flush();
                    os.close();
                }
                // connect.
                conn.connect();

                final int rc = conn.getResponseCode();
                if (rc < 200 || rc >= 300) {
                    throw new IOException(rc + " : "
                            + conn.getResponseMessage() + " : " + url);
                }

                // if (log.isInfoEnabled())
                // log.info("Status Line: " + conn.getResponseMessage());

                /*
                 * Read the response body
                 */
                {

                    final LineNumberReader r = new LineNumberReader(
                            new InputStreamReader(
                                    conn.getInputStream(),
                                    conn.getContentEncoding() == null ? "ISO-8859-1"
                                            : conn.getContentEncoding()));
                    try {
                        String s;
                        while ((s = r.readLine()) != null) {
                            if (log.isInfoEnabled())
                                log.info(s);
                        }
                    } finally {
                        r.close();
                    }

                }

                // final long elapsed = System.nanoTime() - begin;

            } finally {

                // clean up the connection resources
                if (conn != null)
                    conn.disconnect();

            }

        } finally {

            fixture.shutdownNow();

        }

    }

}
